/**
 * Copyright (C) 2013 DaiKit.com - daikit4gxt-sample module (admin@daikit.com)
 *
 *         Project home : http://code.daikit.com/commons
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.daikit.daikit4gxtsample.server.service.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.daikit.commons.shared.bean.AbstractDkBeanWithId;
import com.daikit.commons.shared.exception.DkException;
import com.daikit.daikit4gxtsample.server.service.ProductService;
import com.daikit.daikit4gxtsample.shared.bean.CityData;
import com.daikit.daikit4gxtsample.shared.bean.CountryDetailData;
import com.daikit.daikit4gxtsample.shared.bean.ProductData;
import com.daikit.daikit4gxtsample.shared.bean.ProductSpecialData;
import com.daikit.daikit4gxtsample.shared.bean.ShopData;
import com.daikit.daikit4gxtsample.shared.bean.StockDetailData;
import com.daikit.daikit4gxtsample.shared.bean.SupplierAvailabilityData;
import com.daikit.daikit4gxtsample.shared.bean.SupplierDetailData;
import com.daikit.daikit4gxtsample.shared.bean.WarehouseData;
import com.sencha.gxt.data.shared.loader.PagingLoadConfig;
import com.sencha.gxt.data.shared.loader.PagingLoadResult;
import com.sencha.gxt.data.shared.loader.PagingLoadResultBean;


@SuppressWarnings("javadoc")
@Service
public class ProductServiceImpl implements ProductService
{

	private static final Log LOG = LogFactory.getLog(ProductServiceImpl.class);

	@Override
	public PagingLoadResult<ProductData> getProducts(final PagingLoadConfig pagingLoadConfig) throws DkException
	{
		return getPagedProducts(pagingLoadConfig, DataStore.PRODUCTS);
	}

	@Override
	public PagingLoadResult<ProductData> searchProducts(final String searchInput, final PagingLoadConfig pagingLoadConfig)
	{
		final List<ProductData> filtered = new ArrayList<ProductData>();
		if (StringUtils.hasText(searchInput))
		{
			for (final ProductData product : DataStore.PRODUCTS)
			{
				if (product.getName().contains(searchInput))
				{
					filtered.add(product);
				}
			}
		}
		else
		{
			//			final SortInfo sort = pagingLoadConfig.getSortInfo().isEmpty() ? null : pagingLoadConfig.getSortInfo().get(0);
			//			final SortDir sortDir = sort == null ? null : sort.getSortDir();
			filtered.addAll(DataStore.PRODUCTS);
		}
		return getPagedProducts(pagingLoadConfig, filtered);
	}

	private PagingLoadResult<ProductData> getPagedProducts(final PagingLoadConfig pagingLoadConfig,
			final List<ProductData> dataStore)
	{
		final List<ProductData> products = new ArrayList<ProductData>();
		for (int i = pagingLoadConfig.getOffset(); i < pagingLoadConfig.getOffset() + pagingLoadConfig.getLimit(); i++)
		{
			if (i < dataStore.size())
			{
				products.add(dataStore.get(i));
			}
		}
		return new PagingLoadResultBean<ProductData>(products, dataStore.size(), pagingLoadConfig.getOffset());
	}

	@Override
	public ProductData saveProduct(final ProductData productData) throws DkException
	{
		LOG.debug("Product to be saved : " + productData);

		ProductData fromDatabase = null;

		if (productData.isCreation()) // Creation first
		{
			LOG.debug("Product is created");
			fromDatabase = new ProductData();
		}
		else if (productData.isDirty()) // Dirty second
		{
			LOG.debug("Product is updated");
			fromDatabase = DataStore.getProductById(productData.getId());
		}

		// Dirty and Creation
		if (productData.isDirty())
		{
			LOG.debug("Product is dirty");
			for (final String dirtyPath : productData.getDirtyPaths())
			{
				if ("name".equals(dirtyPath))
				{
					LOG.debug("Product name is dirty : old value = [" + fromDatabase.getName() + "] new value = ["
							+ productData.getName() + "]");
					fromDatabase.setName(productData.getName());
				}
				else if ("property1".equals(dirtyPath))
				{
					LOG.debug("Product property1 is dirty : old value = [" + fromDatabase.getProperty1() + "] new value = ["
							+ productData.getProperty1() + "]");
					fromDatabase.setProperty1(productData.getProperty1());
				}
				else if ("property2".equals(dirtyPath))
				{
					LOG.debug("Product property2 is dirty : old value = [" + fromDatabase.getProperty2() + "] new value = ["
							+ productData.getProperty2() + "]");
					fromDatabase.setProperty2(productData.getProperty2());
				}
				else if ("property3".equals(dirtyPath))
				{
					LOG.debug("Product property3 is dirty : old value = [" + fromDatabase.getProperty3() + "] new value = ["
							+ productData.getProperty3() + "]");
					fromDatabase.setProperty3(productData.getProperty3());
				}
				else if ("property4".equals(dirtyPath))
				{
					LOG.debug("Product property4 is dirty : old value = [" + fromDatabase.getProperty4() + "] new value = ["
							+ productData.getProperty4() + "]");
					fromDatabase.setProperty4(productData.getProperty4());
				}
				else if ("propertyBoolean".equals(dirtyPath))
				{
					LOG.debug("Product propertyBoolean is dirty : old value = [" + fromDatabase.getPropertyBoolean()
							+ "] new value = [" + productData.getPropertyBoolean() + "]");
					fromDatabase.setPropertyBoolean(productData.getPropertyBoolean());
				}
				else if ("propertyRichText".equals(dirtyPath))
				{
					LOG.debug("Product propertyRichText is dirty : old value = [" + fromDatabase.getPropertyRichText()
							+ "] new value = [" + productData.getPropertyRichText() + "]");
					fromDatabase.setPropertyRichText(productData.getPropertyRichText());
				}
				else if ("productDetail".equals(dirtyPath))
				{
					LOG.debug("Product productDetail is dirty : old value = [" + fromDatabase.getProductDetail().getAsSimpleString()
							+ "] new value = [" + productData.getProductDetail() + "]");
					fromDatabase.setProductDetail(productData.getProductDetail());
				}
				else if ("localizedProperty".equals(dirtyPath))
				{
					fromDatabase.getLocalizedProperty().clear();
					fromDatabase.getLocalizedProperty().putAll(productData.getLocalizedProperty());
				}
				else if ("supplierDetails".equals(dirtyPath))
				{
					LOG.debug("Product supplierDetails is dirty");
					for (final SupplierDetailData supplierDetail : productData.getSupplierDetails())
					{
						saveSupplierDetail(fromDatabase, supplierDetail);
					}
				}
				else if ("countryDetails".equals(dirtyPath))
				{
					LOG.debug("Product countryDetails is dirty");
					fromDatabase.getCountryDetails().clear();
					for (final CountryDetailData supplierDetail : productData.getCountryDetails())
					{
						fromDatabase.getCountryDetails().add(supplierDetail);
					}
				}
				else if ("comments".equals(dirtyPath))
				{
					LOG.debug("Product comments is dirty");
					fromDatabase.setComments(productData.getComments());
				}

				if (productData instanceof ProductSpecialData)
				{
					if ("special".equals(dirtyPath))
					{
						LOG.debug("Product special is dirty : old value = [" + ((ProductSpecialData) fromDatabase).getSpecial()
								+ "] new value = [" + ((ProductSpecialData) productData).getSpecial() + "]");
						((ProductSpecialData) fromDatabase).setSpecial(((ProductSpecialData) productData).getSpecial());
					}
				}
			}
			// delete
			for (final Entry<String, Set<String>> suppressedEntry : productData.getDeletedChildrenIds().entrySet())
			{
				LOG.debug("Product has children suppressed : " + suppressedEntry);
				if ("supplierDetails".equals(suppressedEntry.getKey()))
				{
					for (final String suppressedSupplierDetailId : suppressedEntry.getValue())
					{
						final SupplierDetailData supplierDetail = DataStore.getSupplierDetailById(suppressedSupplierDetailId);
						if (supplierDetail == null)
						{
							LOG.debug("supplierDetailData is suppressed but was not in product before -> do nothing");
						}
						else
						{
							LOG.debug("supplierDetailData is suppressed : " + supplierDetail);
							fromDatabase.getSupplierDetails().remove(supplierDetail);
						}
					}
				}
			}
		}

		// SAVE
		if (productData.isCreation())
		{
			DataStore.PRODUCTS.add(fromDatabase);
			LOG.debug("Product saved : " + fromDatabase);
		}
		else if (productData.isDirty())
		{
			LOG.debug("Product saved : " + fromDatabase);
		}
		else
		{
			LOG.debug("Product is not modified, no save necessary : " + productData);
			fromDatabase = DataStore.getProductById(productData.getId());
		}
		return fromDatabase;
	}

	private void saveSupplierDetail(final ProductData productFromDatabase, final SupplierDetailData supplierDetailData)
	{
		SupplierDetailData fromDatabase = null;

		if (supplierDetailData.isCreation())
		{
			LOG.debug("supplierDetailData is created : " + supplierDetailData);
			fromDatabase = new SupplierDetailData();
		}
		else if (supplierDetailData.isDirty())
		{
			fromDatabase = DataStore.getSupplierDetailById(supplierDetailData.getId());
			LOG.debug("supplierDetailData is dirty : old value = [" + AbstractDkBeanWithId.asSimpleString(fromDatabase)
					+ "] + new value = [" + AbstractDkBeanWithId.asSimpleString(supplierDetailData) + "]");
		}
		if (supplierDetailData.isDirty())
		{
			for (final String dirtyPath : supplierDetailData.getDirtyPaths())
			{
				if ("name".equals(dirtyPath))
				{
					LOG.debug("supplierDetailData[" + supplierDetailData.getId() + "] name is dirty : old value = ["
							+ fromDatabase.getName() + "] new value = [" + supplierDetailData.getName() + "]");
					fromDatabase.setName(supplierDetailData.getName());
				}
				else if ("supplier".equals(dirtyPath))
				{
					LOG.debug("supplierDetailData[" + supplierDetailData.getId() + "] supplier is dirty : old value = ["
							+ AbstractDkBeanWithId.asSimpleString(fromDatabase.getSupplier()) + "] new value = ["
							+ AbstractDkBeanWithId.asSimpleString(supplierDetailData.getSupplier()) + "]");
					fromDatabase.setName(supplierDetailData.getName());
				}
				else if ("warehouse".equals(dirtyPath))
				{
					LOG.debug("supplierDetailData[" + supplierDetailData.getId() + "] name is dirty : old value = ["
							+ AbstractDkBeanWithId.asSimpleString(fromDatabase.getWarehouse()) + "] new value = ["
							+ AbstractDkBeanWithId.asSimpleString(supplierDetailData.getWarehouse()) + "]");
					fromDatabase.setWarehouse(supplierDetailData.getWarehouse());
				}
				else if ("stockDetails".equals(dirtyPath))
				{
					LOG.debug("supplierDetailData[" + supplierDetailData.getId() + "] stockDetails is dirty");
					for (final StockDetailData stockDetail : supplierDetailData.getStockDetails())
					{
						saveStockDetail(fromDatabase, stockDetail);
					}
				}
				else if ("availability".equals(dirtyPath))
				{
					LOG.debug("supplierDetailData[" + supplierDetailData.getId() + "] startDate is dirty");
					saveSupplierDetailAvailability(fromDatabase, supplierDetailData.getAvailability());
				}
			}
			// delete
			for (final Entry<String, Set<String>> suppressedEntry : supplierDetailData.getDeletedChildrenIds().entrySet())
			{
				LOG.debug("supplierDetailData[" + supplierDetailData.getId() + "] has children suppressed : " + suppressedEntry);
				if ("stockDetails".equals(suppressedEntry.getKey()))
				{
					for (final String suppressedStockDetailId : suppressedEntry.getValue())
					{
						final StockDetailData existingStockDetail = DataStore.getStockDetailById(suppressedStockDetailId);
						if (existingStockDetail == null)
						{
							LOG.debug("stockDetail is suppressed but was not in supplierDetail before -> do nothing");
						}
						else
						{
							LOG.debug("stockDetail is suppressed : " + existingStockDetail);
							fromDatabase.getStockDetails().remove(existingStockDetail);
						}
					}
				}
			}
		}
		// SAVE
		if (supplierDetailData.isCreation())
		{
			productFromDatabase.getSupplierDetails().add(fromDatabase);
			LOG.debug("supplierDetailData saved : " + fromDatabase);
		}
		else if (supplierDetailData.isDirty())
		{
			LOG.debug("supplierDetailData saved : " + fromDatabase);
		}
		else
		{
			LOG.debug("supplierDetailData is not modified, no save necessary : " + supplierDetailData);
		}
	}

	private void saveSupplierDetailAvailability(final SupplierDetailData parent,
			final SupplierAvailabilityData supplierAvailabilityData)
	{
		SupplierAvailabilityData fromDatabase = null;
		if (supplierAvailabilityData.isCreation())
		{
			LOG.debug("SupplierAvailabilityData is created : " + supplierAvailabilityData);
			fromDatabase = new SupplierAvailabilityData();
		}
		else if (supplierAvailabilityData.isDirty())
		{
			fromDatabase = DataStore.getSupplierAvailabilityBySupplierDetailId(parent.getId());
			LOG.debug("supplierDetailData is dirty : old value = [" + AbstractDkBeanWithId.asSimpleString(fromDatabase)
					+ "] new value = [" + AbstractDkBeanWithId.asSimpleString(supplierAvailabilityData) + "]");
		}
		if (supplierAvailabilityData.isDirty())
		{
			for (final String dirtyPath : supplierAvailabilityData.getDirtyPaths())
			{
				if ("startDate".equals(dirtyPath))
				{
					LOG.debug("supplierAvailabilityData[" + supplierAvailabilityData.getId() + "] startDate is dirty : old value = ["
							+ fromDatabase.getStartDate() + "] new value = [" + supplierAvailabilityData.getStartDate() + "]");
					fromDatabase.setStartDate(supplierAvailabilityData.getStartDate());
				}
				if ("endDate".equals(dirtyPath))
				{
					LOG.debug("supplierAvailabilityData[" + supplierAvailabilityData.getId() + "] endDate is dirty : old value = ["
							+ fromDatabase.getEndDate() + "] new value = [" + supplierAvailabilityData.getEndDate() + "]");
					fromDatabase.setEndDate(supplierAvailabilityData.getEndDate());
				}
			}
		}
	}

	private void saveStockDetail(final SupplierDetailData supplierDetailFromDatabase, final StockDetailData stockDetailData)
	{
		StockDetailData fromDatabase = null;
		if (stockDetailData.isCreation())
		{
			LOG.debug("stockDetail is created : " + fromDatabase);
			fromDatabase = new StockDetailData();
		}
		else if (stockDetailData.isDirty())
		{
			fromDatabase = DataStore.getStockDetailById(stockDetailData.getId());
			LOG.debug("stockDetail is dirty : old value = [" + fromDatabase.getAsSimpleString() + "] + new value = ["
					+ stockDetailData.getAsSimpleString() + "]");

			for (final String dirtyPath : stockDetailData.getDirtyPaths())
			{
				if ("name".equals(dirtyPath))
				{
					LOG.debug("stockDetail[" + stockDetailData.getId() + "] name is dirty : old value = [" + fromDatabase.getName()
							+ "] new value = [" + stockDetailData.getName() + "]");
					fromDatabase.setName(stockDetailData.getName());
				}
				else if ("quantity".equals(dirtyPath))
				{
					LOG.debug("stockDetail[" + stockDetailData.getId() + "] quantity is dirty : old value = ["
							+ fromDatabase.getQuantity() + "] new value = [" + stockDetailData.getQuantity() + "]");
					fromDatabase.setQuantity(stockDetailData.getQuantity());
				}
			}
		}
		// SAVE
		if (stockDetailData.isCreation())
		{
			supplierDetailFromDatabase.getStockDetails().add(fromDatabase);
			LOG.debug("stockDetail saved : " + fromDatabase);
		}
		else if (stockDetailData.isDirty())
		{
			LOG.debug("stockDetail saved : " + fromDatabase);
		}
		else
		{
			LOG.debug("stockDetail is not modified, no save necessary : " + stockDetailData);
		}
	}

	@Override
	public PagingLoadResult<WarehouseData> getWarehouses(final String inputText, final PagingLoadConfig pagingLoadConfig)
			throws DkException
	{
		return getPagingData(inputText, DataStore.WAREHOUSES, pagingLoadConfig);
	}

	@Override
	public PagingLoadResult<CountryDetailData> getCountryDetails(final String inputText, final PagingLoadConfig pagingLoadConfig)
	{
		return getPagingData(inputText, DataStore.COUNTRYDETAILS, pagingLoadConfig);
	}

	@Override
	public PagingLoadResult<CityData> getCities(final String inputText, final PagingLoadConfig pagingLoadConfig)
			throws DkException
	{
		return getPagingData(inputText, DataStore.CITIES, pagingLoadConfig);
	}

	@Override
	public PagingLoadResult<ShopData> getShops(final String inputText, final PagingLoadConfig pagingLoadConfig) throws DkException
	{
		return getPagingData(inputText, DataStore.SHOPS, pagingLoadConfig);
	}

	private <T extends AbstractDkBeanWithId> PagingLoadResult<T> getPagingData(final String inputText, final List<T> datas,
			final PagingLoadConfig pagingLoadConfig) throws DkException
	{
		final List<T> filtered = new ArrayList<T>();
		final List<T> result = new ArrayList<T>();
		// First , filter
		if (StringUtils.hasText(inputText))
		{
			for (final T data : datas)
			{
				if (data.getAsSimpleString().contains(inputText))
				{
					filtered.add(data);
				}
			}
		}
		else
		{
			filtered.addAll(datas);
		}
		// Then get page
		for (int i = pagingLoadConfig.getOffset(); i < pagingLoadConfig.getOffset() + pagingLoadConfig.getLimit()
				&& i < filtered.size(); i++)
		{
			result.add(filtered.get(i));
		}
		return new PagingLoadResultBean<T>(result, filtered.size(), pagingLoadConfig.getOffset());
	}

}
